/*
 *  Hamlib CI-V backend - OptoScan extensions
 *  Copyright (c) 2000-2005 by Stephane Fillod
 *
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>  /* String function definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <math.h>
#include <sys/time.h>

#include "hamlib/rig.h"
#include "serial.h"
#include "misc.h"
#include "cal.h"
#include "token.h"

#include "icom.h"
#include "icom_defs.h"
#include "frame.h"
#include "optoscan.h"


const struct confparams opto_ext_parms[] = {
  { TOK_TAPECNTL, "tapecntl", "Toggle Tape Switch", "Toggles built in tape switch", 0, RIG_CONF_CHECKBUTTON, {} },
  { TOK_5KHZWIN,  "5khzwin", "Toggle 5kHz Search Window", "Toggles 5kHz search window", 0, RIG_CONF_CHECKBUTTON, {} },
  { TOK_SPEAKER,  "speaker", "Toggle speaker audio", "Toggles speaker audio", 0, RIG_CONF_CHECKBUTTON, {} },
  { TOK_AUDIO, "audio", "Audio present", "Audio present", NULL, RIG_CONF_CHECKBUTTON, {} },
  { TOK_DTMFPEND, "dtmfpend", "DTMF Digit Pending", "DTMF Digit Pending", NULL, RIG_CONF_CHECKBUTTON, {} },
  { TOK_DTMFOVRR, "dtmfovrr", "DTMF Buffer Overflow", "DTMF Buffer Overflow", NULL, RIG_CONF_CHECKBUTTON, {} },
  { TOK_CTCSSACT, "ctcssact", "CTCSS Tone Active", "CTCSS Tone Active", NULL, RIG_CONF_CHECKBUTTON, {} },
  { TOK_DCSACT,   "dcsact", "DCS Code Active", "DCS Code Active", NULL, RIG_CONF_CHECKBUTTON, {} },
  { RIG_CONF_END, NULL, }
};

static int optoscan_get_status_block(RIG *rig, struct optostat *status_block);
static int optoscan_send_freq(RIG *rig,pltstate_t *state);
static int optoscan_RTS_toggle(RIG *rig);
static int optoscan_start_timer(RIG *rig, pltstate_t *state);
static int optoscan_wait_timer(RIG *rig, pltstate_t *state);

/*
 * optoscan_open
 * Assumes rig!=NULL, rig->state.priv!=NULL
 */
int optoscan_open(RIG *rig)
{
		struct icom_priv_data *priv;
		struct rig_state *rs;
  		pltstate_t *pltstate;
		unsigned char ackbuf[16];
		int ack_len, retval;

		rs = &rig->state;
		priv = (struct icom_priv_data*)rs->priv;

		pltstate = malloc(sizeof(pltstate_t));
		if (!pltstate) {
			return -RIG_ENOMEM;
		}
		memset(pltstate, 0, sizeof(pltstate_t));
		priv->pltstate = pltstate;

		/* select REMOTE control */
		retval = icom_transaction (rig, C_CTL_MISC, S_OPTO_REMOTE,
						NULL, 0, ackbuf, &ack_len);
		if (retval != RIG_OK) {
			free(pltstate);
			return retval;
		}

		if (ack_len != 1 || ackbuf[0] != ACK) {
				rig_debug(RIG_DEBUG_ERR,"optoscan_open: ack NG (%#.2x), "
								"len=%d\n", ackbuf[0], ack_len);
				free(pltstate);
				return -RIG_ERJCTED;
		}

		return RIG_OK;
}

/*
 * optoscan_close
 * Assumes rig!=NULL, rig->state.priv!=NULL
 */
int optoscan_close(RIG *rig)
{
		struct icom_priv_data *priv;
		struct rig_state *rs;
		unsigned char ackbuf[16];
		int ack_len, retval;

		rs = &rig->state;
		priv = (struct icom_priv_data*)rs->priv;

		/* select LOCAL control */
		retval = icom_transaction (rig, C_CTL_MISC, S_OPTO_LOCAL,
						NULL, 0, ackbuf, &ack_len);
		if (retval != RIG_OK)
				return retval;

		if (ack_len != 1 || ackbuf[0] != ACK) {
				rig_debug(RIG_DEBUG_ERR,"optoscan_close: ack NG (%#.2x), "
								"len=%d\n", ackbuf[0], ack_len);
				return -RIG_ERJCTED;
		}

		free(priv->pltstate);

		return RIG_OK;
}

/*
 * optoscan_get_info
 * Assumes rig!=NULL, rig->state.priv!=NULL
 */
const char* optoscan_get_info(RIG *rig)
{
		unsigned char ackbuf[16];
		int ack_len, retval;
		static char info[64];

		/* select LOCAL control */
		retval = icom_transaction (rig, C_CTL_MISC, S_OPTO_RDID,
						NULL, 0, ackbuf, &ack_len);
		if (retval != RIG_OK)
				return NULL;

		if (ack_len != 7) {
				rig_debug(RIG_DEBUG_ERR,"optoscan_get_info: ack NG (%#.2x), "
								"len=%d\n", ackbuf[0], ack_len);
				return NULL;
		}

		sprintf(info, "OptoScan%c%c%c, software version %d.%d, "
						"interface version %d.%d\n",
						ackbuf[2], ackbuf[3], ackbuf[4],
						ackbuf[5] >> 4, ackbuf[5] & 0xf,
						ackbuf[6] >> 4, ackbuf[6] & 0xf);

		return info;
}

/*
 * optoscan_get_ctcss_tone
 * Assumes rig!=NULL, rig->state.priv!=NULL
 */
int optoscan_get_ctcss_tone(RIG *rig, vfo_t vfo, tone_t *tone)
{
		unsigned char tonebuf[MAXFRAMELEN];
		int tone_len, retval;

		retval = icom_transaction(rig, C_CTL_MISC, S_OPTO_RDCTCSS, NULL, 0,
												tonebuf, &tone_len);
		if (retval != RIG_OK)
				return retval;

		if (tone_len != 4) {
				rig_debug(RIG_DEBUG_ERR,"optoscan_get_ctcss_tone: ack NG (%#.2x), "
								"len=%d\n", tonebuf[0], tone_len);
				return -RIG_ERJCTED;
		}

		tone_len -= 2;

		*tone = from_bcd_be(tonebuf+2, tone_len*2);
		rig_debug(RIG_DEBUG_ERR,"optoscan_get_ctcss_tone: *tone=%d\n",*tone);

		return RIG_OK;
}


/*
 * optoscan_get_dcs_code
 * Assumes rig!=NULL, rig->state.priv!=NULL
 */
int optoscan_get_dcs_code(RIG * rig, vfo_t vfo, tone_t *code)
{
		unsigned char tonebuf[MAXFRAMELEN];
		int tone_len, retval;

		retval = icom_transaction(rig, C_CTL_MISC, S_OPTO_RDDCS, NULL, 0,
												tonebuf, &tone_len);
		if (retval != RIG_OK)
				return retval;

		if (tone_len != 4) {
				rig_debug(RIG_DEBUG_ERR,"optoscan_get_dcs_code: ack NG (%#.2x), "
								"len=%d\n", tonebuf[0], tone_len);
				return -RIG_ERJCTED;
		}

		tone_len -= 2;

		*code = from_bcd_be(tonebuf+2, tone_len*2);
		rig_debug(RIG_DEBUG_ERR,"optoscan_get_dcs_code: *code=%d\n",*code);

        return RIG_OK;
}

int optoscan_recv_dtmf(RIG *rig, vfo_t vfo, char *digits, int *length)
{
		unsigned char dtmfbuf[MAXFRAMELEN],digit;
		int len, retval, digitpos;
		unsigned char xlate[] = {'0','1','2','3','4','5','6',
					 '7','8','9','A','B','C','D',
					 '*','#'};
		digitpos=0;

		do {
		  retval = icom_transaction(rig, C_CTL_MISC, S_OPTO_RDDTMF,
					    NULL,0,dtmfbuf, &len);
		  if (retval != RIG_OK)
		    return retval;

		  if (len != 3) {
		    rig_debug(RIG_DEBUG_ERR,"optoscan_recv_dtmf: ack NG (%#.2x), len=%d\n", dtmfbuf[0], len);
		    return -RIG_ERJCTED;
		  }

		  digit = dtmfbuf[2];

		  if( digit < 0x16 )
		    {
		      digits[digitpos] = xlate[digit];
		      digitpos++;
		    }
		} while( (digit != 0x99) && (digitpos < *length) );

		*length = digitpos;
		digits[digitpos]=0;

		if(*length > 0)
		  {
		    rig_debug(RIG_DEBUG_ERR,"optoscan_recv_dtmf: %d digits - %s\n",*length,digits);
		  }
		else
		  {
		    rig_debug(RIG_DEBUG_ERR,"optoscan_recv_dtmf: no digits to read.\n");
		  }

        return RIG_OK;
}

/*
 * Assumes rig!=NULL, rig->state.priv!=NULL
 */
int optoscan_set_ext_parm(RIG *rig, token_t token, value_t val)
{
	unsigned char epbuf[MAXFRAMELEN], ackbuf[MAXFRAMELEN];
	int ack_len;
	int retval,subcode;

	memset(epbuf,0,MAXFRAMELEN);
	memset(ackbuf,0,MAXFRAMELEN);

	switch(token) {
	case TOK_TAPECNTL:
	  if( val.i == 0 ) {
	    subcode = S_OPTO_TAPE_OFF;
	  }
	  else {
	    subcode = S_OPTO_TAPE_ON;
	  }
	  break;
	case TOK_5KHZWIN:
	  if( val.i == 0 ) {
	    subcode = S_OPTO_5KSCOFF;
	  }
	  else {
	    subcode = S_OPTO_5KSCON;
	  }
	  break;
	case TOK_SPEAKER:
	  if( val.i == 0 ) {
	    subcode = S_OPTO_SPKROFF;
	  }
	  else {
	    subcode = S_OPTO_SPKRON;
	  }
	  break;
	default:
	  return -RIG_EINVAL;
	}

	retval = icom_transaction (rig, C_CTL_MISC, subcode, epbuf, 0,
						ackbuf, &ack_len);
	if (retval != RIG_OK)
		return retval;

	if (ack_len != 1 || ackbuf[0] != ACK) {
		rig_debug(RIG_DEBUG_ERR, "%s: ack NG (%#.2x), "
			"len=%d\n", __FUNCTION__, ackbuf[0], ack_len);
		return -RIG_ERJCTED;
	}

	return RIG_OK;
}

/*
 * Assumes rig!=NULL, rig->state.priv!=NULL
 *  and val points to a buffer big enough to hold the conf value.
 */
int optoscan_get_ext_parm(RIG *rig, token_t token, value_t *val)
{
  struct optostat status_block;
  int retval;

  retval = optoscan_get_status_block(rig,&status_block);

  if (retval != RIG_OK)
    return retval;

  switch(token) {
  case TOK_TAPECNTL:
    val->i = status_block.tape_enabled;
    break;
  case TOK_5KHZWIN:
    val->i = status_block.fivekhz_enabled;
    break;
  case TOK_SPEAKER:
    val->i = status_block.speaker_enabled;
    break;
  case TOK_AUDIO:
    val->i = status_block.audio_present;
    break;
  case TOK_DTMFPEND:
    val->i = status_block.DTMF_pending;
    break;
  case TOK_DTMFOVRR:
    val->i = status_block.DTMF_overrun;
    break;
  case TOK_CTCSSACT:
    val->i = status_block.CTCSS_active;
    break;
  case TOK_DCSACT:
    val->i = status_block.DCS_active;
    break;
  default:
    return -RIG_ENIMPL;
  }

  return RIG_OK;
}

/*
 * optoscan_set_level
 * Assumes rig!=NULL, rig->state.priv!=NULL
 */
int optoscan_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val)
{
		unsigned char lvlbuf[MAXFRAMELEN], ackbuf[MAXFRAMELEN];
		int ack_len;
		int lvl_cn, lvl_sc;		/* Command Number, Subcommand */
		int icom_val;
		int retval;

		memset(lvlbuf,0,MAXFRAMELEN);

		/*
		 * So far, levels of float type are in [0.0..1.0] range
		 */
		if (RIG_LEVEL_IS_FLOAT(level))
				icom_val = val.f * 255;
		else
				icom_val = val.i;

		switch (level) {
		case RIG_LEVEL_AF:
			lvl_cn = C_CTL_MISC;
			if( icom_val == 0 ) {
			  lvl_sc = S_OPTO_SPKROFF;
			}
			else {
			  lvl_sc = S_OPTO_SPKRON;
			}
			break;
		default:
			rig_debug(RIG_DEBUG_ERR,"Unsupported set_level %d", level);
			return -RIG_EINVAL;
		}

		retval = icom_transaction (rig, lvl_cn, lvl_sc, lvlbuf, 0,
						ackbuf, &ack_len);
		if (retval != RIG_OK)
				return retval;

		if (ack_len != 1 || ackbuf[0] != ACK) {
				rig_debug(RIG_DEBUG_ERR,"optoscan_set_level: ack NG (%#.2x), "
								"len=%d\n", ackbuf[0], ack_len);
				return -RIG_ERJCTED;
		}

		return RIG_OK;
}

/*
 * optoscan_get_level
 * Assumes rig!=NULL, rig->state.priv!=NULL, val!=NULL
 */
int optoscan_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val)
{
  struct optostat status_block;
  unsigned char lvlbuf[MAXFRAMELEN];
  int lvl_len;
  int lvl_cn, lvl_sc;		/* Command Number, Subcommand */
  int icom_val;
  int cmdhead;
  int retval;

  if( level != RIG_LEVEL_AF )
    {
      switch (level) {
      case RIG_LEVEL_RAWSTR:
	lvl_cn = C_RD_SQSM;
	lvl_sc = S_SML;
	break;
      default:
	rig_debug(RIG_DEBUG_ERR,"Unsupported get_level %d", level);
	return -RIG_EINVAL;
      }

      retval = icom_transaction (rig, lvl_cn, lvl_sc, NULL, 0,
				 lvlbuf, &lvl_len);
      if (retval != RIG_OK)
	return retval;

      /*
       * strbuf should contain Cn,Sc,Data area
       */
      cmdhead = (lvl_sc == -1) ? 1:2;
      lvl_len -= cmdhead;

      if (lvlbuf[0] != ACK && lvlbuf[0] != lvl_cn) {
	rig_debug(RIG_DEBUG_ERR,"optoscan_get_level: ack NG (%#.2x), "
		  "len=%d\n", lvlbuf[0],lvl_len);
	return -RIG_ERJCTED;
      }

      /*
       * The result is a 3 digit BCD, but in *big endian* order: 0000..0255
       * (from_bcd is little endian)
       */
      icom_val = from_bcd_be(lvlbuf+cmdhead, lvl_len*2);
    }
  else /* level == RIG_LEVEL_AF */
    {
      retval = optoscan_get_status_block(rig,&status_block);

      if (retval != RIG_OK)
	return retval;

      icom_val = 0;
      if( status_block.speaker_enabled == 1 )
	icom_val = 255;
    }

  switch (level) {
  case RIG_LEVEL_RAWSTR:
    val->i = icom_val;
    break;
  default:
    if (RIG_LEVEL_IS_FLOAT(level))
      val->f = (float)icom_val/255;
    else
      val->i = icom_val;
  }

  rig_debug(RIG_DEBUG_TRACE,"optoscan_get_level: %d %d %d %f\n", lvl_len,
	    icom_val, val->i, val->f);

  return RIG_OK;
}

/* OS456 Pipeline tuning algorithm:
 *	Step 2:  Send the next frequency and mode to the receiver using the
 *		 TRANSFER NEXT FREQUENCY/MODE command.
 *
 *      Step 3:  Change the state of the RTS interface signal to cause the
 *               next frequency and mode to become the current frequency and
 *               mode, and the receiver to begin settling.
 *
 *	Step 4:  While the receiver is still settling on the current
 *		 frequency and mode, send the next frequency and mode to the
 *		 receiver using the TRANSFER NEXT FREQUENCY/MODE command.
 *
 *	Step 5:  Wait for the receiver to finish settling.  The total
 *		 settling time, including sending the next frequency and
 *		 mode, is 20 milliseconds (0.02 seconds).
 *
 *	Step 6:  Check the squelch status by reading the DCD interface
 *		 signal.  If the squelch is open, scanning is stopped.
 *		 Otherwise, scanning continues.  Optionally, the status of
 *		 the CTCSS/DCS/DTMF decoder can be checked, and the
 *		 appropriate action taken.
 *
 *	Step 7:  Continuously repeat steps 3 through 6 above.
 */
int optoscan_scan(RIG *rig, vfo_t vfo, scan_t scan, int ch)
{
  pltstate_t *state;
  pltune_cb_t cb;
  int rc, pin_state;
  struct rig_state *rs;

  if(scan != RIG_SCAN_PLT)
    return -RIG_ENAVAIL;

  rs=&rig->state;
  cb = rig->callbacks.pltune;
  state = ((struct icom_priv_data*)rs->priv)->pltstate;

  if(state==NULL)
    return -RIG_EINTERNAL;

  if(state->freq==0) /* pltstate_t is not initialized - perform setup */
    {
      /* time for CIV command to be sent. this is subtracted from */
      /* rcvr settle time */
      state->usleep_time = (1000000 / (rig->state.rigport.parm.serial.rate))
	* 13 * 9;

      rc=cb(rig,vfo,&(state->next_freq),&(state->next_mode),
	    &(state->next_width),rig->callbacks.pltune_arg);
      if(rc==RIG_SCAN_STOP)
	return RIG_OK; /* callback halted loop */

      /* Step 1 is implicit, since hamlib does this when it opens the device */
      optoscan_send_freq(rig,state); /*Step 2*/
    }

  rc=!RIG_SCAN_STOP;
  while(rc!=RIG_SCAN_STOP)
    {
      optoscan_RTS_toggle(rig); /*Step 3*/

      state->freq = state->next_freq;
      state->mode = state->next_mode;

      optoscan_start_timer(rig,state);

      rc=cb(rig,vfo,&(state->next_freq),&(state->next_mode),
	    &(state->next_width),rig->callbacks.pltune_arg);
      if(rc!=RIG_SCAN_STOP)
	{
	  optoscan_send_freq(rig,state); /*Step 4*/
	}

      optoscan_wait_timer(rig,state); /*Step 5*/

      ser_get_car(&rs->rigport,&pin_state);
      if( pin_state ) /*Step 6*/
	{
	  return RIG_OK; /* we've broken squelch - return(). caller can */
	                 /* get current freq & mode out of state str    */
	}
    }

  /* exiting pipeline loop - force state init on next call */
  state->freq=0;

  return RIG_OK;
}

/*
 * Assumes rig!=NULL, status_block !=NULL
 */
static int optoscan_get_status_block(RIG *rig, struct optostat *status_block)
{
  int retval, ack_len, expected_len;
  unsigned char ackbuf[MAXFRAMELEN];

  memset(status_block,0,sizeof(struct optostat));

  retval = icom_transaction (rig, C_CTL_MISC, S_OPTO_RDSTAT, NULL, 0,
			     ackbuf, &ack_len);

  if (retval != RIG_OK)
    return retval;

  switch( rig->caps->rig_model )
    {
    case RIG_MODEL_OS456:
      expected_len=4;
      break;
    case RIG_MODEL_OS535:
      expected_len=5;
      break;
    default:
      rig_debug(RIG_DEBUG_ERR,"optoscan_get_status_block: unknown rig model");
      return -RIG_ERJCTED;
      break;
    }

  if (ack_len != expected_len ) {
    rig_debug(RIG_DEBUG_ERR,"optoscan_get_status_block: ack NG (%#.2x), "
	      "len=%d\n", ackbuf[0], ack_len);
    return -RIG_ERJCTED;
  }

  if( ackbuf[2] & 1 )  status_block->remote_control = 1;
  if( ackbuf[2] & 2 )  status_block->DTMF_pending = 1;
  if( ackbuf[2] & 4 )  status_block->DTMF_overrun = 1;
  if( ackbuf[2] & 16 ) status_block->squelch_open = 1;
  if( ackbuf[2] & 32 ) status_block->CTCSS_active = 1;
  if( ackbuf[2] & 64 ) status_block->DCS_active = 1;

  if( ackbuf[3] & 1 )  status_block->tape_enabled = 1;
  if( ackbuf[3] & 2 )  status_block->speaker_enabled = 1;
  if( ackbuf[3] & 4 )  status_block->fivekhz_enabled = 1;
  if( ackbuf[3] & 16 ) status_block->audio_present = 1;

  rig_debug(RIG_DEBUG_VERBOSE,"remote_control     = %d\n",status_block->remote_control);
  rig_debug(RIG_DEBUG_VERBOSE,"DTMF_pending       = %d\n",status_block->DTMF_pending);
  rig_debug(RIG_DEBUG_VERBOSE,"DTMF_overrun       = %d\n",status_block->DTMF_overrun);
  rig_debug(RIG_DEBUG_VERBOSE,"squelch_open       = %d\n",status_block->squelch_open);
  rig_debug(RIG_DEBUG_VERBOSE,"CTCSS_active       = %d\n",status_block->CTCSS_active);
  rig_debug(RIG_DEBUG_VERBOSE,"DCS_active         = %d\n",status_block->DCS_active);
  rig_debug(RIG_DEBUG_VERBOSE,"tape_enabled       = %d\n",status_block->tape_enabled );
  rig_debug(RIG_DEBUG_VERBOSE,"speaker_enabled    = %d\n",status_block->speaker_enabled);
  rig_debug(RIG_DEBUG_VERBOSE,"fivekhz_enabled    = %d\n",status_block->fivekhz_enabled);
  rig_debug(RIG_DEBUG_VERBOSE,"audio_present      = %d\n",status_block->audio_present);

  return RIG_OK;
}


static int optoscan_send_freq(RIG *rig,pltstate_t *state)
{
  unsigned char buff[OPTO_BUFF_SIZE];
  char md,pd;
  freq_t freq;
  rmode_t mode;

  freq=state->next_freq;
  mode=state->next_mode;

  memset(buff,0,OPTO_BUFF_SIZE);

  to_bcd(buff,freq,5*2); /* to_bcd requires nibble len */

  rig2icom_mode(rig,mode,0,(unsigned char *) &md, (signed char *) &pd);
  buff[5]=md;

  /* read echo'd chars only...there will be no ACK from this command
   *
   * Note:
   *	It may have waited fro pltstate->usleep_time before reading the echo'd
   *	chars, but the read will be blocking anyway. --SF
   * */
  return icom_transaction (rig, C_CTL_MISC, S_OPTO_NXT, buff, 6, NULL, NULL);

  return RIG_OK;
}

static int optoscan_RTS_toggle(RIG *rig)
{
  struct rig_state *rs;
  int state=0;

  rs=&rig->state;
  ser_get_rts(&rs->rigport,&state);
  ser_set_rts(&rs->rigport,!state);

  return RIG_OK;
}

static int optoscan_start_timer(RIG *rig, pltstate_t *state)
{
  gettimeofday(&(state->timer_start),NULL);

  return RIG_OK;
}

static int optoscan_wait_timer(RIG *rig, pltstate_t *state)
{
  struct icom_priv_caps *priv_caps;
  int usec_diff;
  int settle_usec;

  priv_caps = (struct icom_priv_caps *)rig->caps->priv;
  settle_usec = priv_caps->settle_time * 1000; /*convert settle time (ms) to */
                                               /* settle time (usec)         */

  gettimeofday(&(state->timer_current),NULL);

  usec_diff = abs( (state->timer_current.tv_usec) -
		   (state->timer_start.tv_usec) );

  if( usec_diff < settle_usec )
    {
      usleep( settle_usec - usec_diff ); /* sleep balance of settle_time */
    }

  return RIG_OK;
}

